Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design doc for operator attribute #2606

Closed

Conversation

reyoung
Copy link
Collaborator

@reyoung reyoung commented Jun 26, 2017

The design doc for operator attribute. Maybe here is better for review.

@reyoung
Copy link
Collaborator Author

reyoung commented Jun 27, 2017

This is a subset of PR #2555, we split #2555 into many parts for review.

```cpp
class OperatorBase {
public:
Error InitializeAttribute(const AttributeReader& attrs) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

virtual

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

explicit AttributeReader(const AttributeMap& attrs) : attrs_(attrs) {}

template <typename T>
Error __must_check Get(const std::string& attributeName, T* attr) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will attr point to if Error happens?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attr is always pointing to an outside variable. The pointer is not modified in Get method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, so the value T* attr point to just not change, is that right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pointer attr is will never be changed inside Get method. The value *attr will not be changed if an error occurred. The value *attr will be set to correct value when there is no error.

public:
Error InitializeAttribute(const AttributeReader& attrs) {
auto err = attrs.Get<float>("scale", &scale_);
if (!err.isOK() && err != "Attribute Not Found") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen if err == "Attribute Not Found" ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the error is Attribute Not Found, then the default value is used.

The default value is set in private data member, like float scale_{1.0};.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think If the error is Attribute Not Found, then the default value is used. should be added to comment.
Another thing is that remind the user to set a default value for their attributes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@jacquesqiao jacquesqiao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Collaborator

@wangkuiyi wangkuiyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the writing could be shortened. Here is my trial:

An operator could have attributes. For example, CosineOp could have a float typed attribute scale, which changes the output range from [-1,1] to [-scale,scale].

Attributes is defined by a name and a type. An instance of an attribute has a value of that type.

As part of the network description, attribute need to be serialized. So we need a protobuf message that describes an attribute, say AttributeProto.

An operator could list its attributes in a data member std::map<string/*name*/, AttributeProto*>.

@@ -0,0 +1,125 @@
# Design Doc about operator attribute

## background
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -0,0 +1,125 @@
# Design Doc about operator attribute
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design Doc: Operator Attributes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


## background

In a neural network, each operator could contain some configurable attributes. For example, a cosine similarity operator may contain an attribute named `scale`. The default cosine similarity returns a value in range [-1.0, 1.0]. But the user can set range scale manually, e.g., user set `scale=5.0`, then that cosine operator will return a value in the range [-5.0, 5.0].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a neural network, each operator could contain some configurable attributes.

==>

Operators have attributes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


In a neural network, each operator could contain some configurable attributes. For example, a cosine similarity operator may contain an attribute named `scale`. The default cosine similarity returns a value in range [-1.0, 1.0]. But the user can set range scale manually, e.g., user set `scale=5.0`, then that cosine operator will return a value in the range [-5.0, 5.0].

The configurable attributes could be various types. Some operators need `float` value to configure; some need `string` value. We need a data structure to represent different types.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

be of various types

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


In a neural network, each operator could contain some configurable attributes. For example, a cosine similarity operator may contain an attribute named `scale`. The default cosine similarity returns a value in range [-1.0, 1.0]. But the user can set range scale manually, e.g., user set `scale=5.0`, then that cosine operator will return a value in the range [-5.0, 5.0].

The configurable attributes could be various types. Some operators need `float` value to configure; some need `string` value. We need a data structure to represent different types.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configurable attributes could be various types. Some operators need float value to configure; some need string value. We need a data structure to represent different types.

==>

Each attributes has a name and a type. We are going to describe them in a protobuf message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


The configurable attributes could be various types. Some operators need `float` value to configure; some need `string` value. We need a data structure to represent different types.

Each operator contains different configurable attributes. The names of attributes are not same. We need an associate map from attribute name to attribute value for `Operator`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each operator contains different configurable attributes. The names of attributes are not same. We need an associate map from attribute name to attribute value for Operator.

==>

We plan to list attributes of an operator in an associate map: map<string, Attribute*>.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In protobuf, the type of Attributes is a map<string, Attribute> attrs; But in each op class, it stores simple strong typed values as member data.

class CosineOp {
public:
  void Init(const AttributeReader& reader) {
    scale_ = reader.Get<float>("scale")
  }

private:
  float scale_;
};

If we extract scale from protobuf map every time when we use it, it is very slow.

```

There are two methods in `AttributeReader`: `Get` and `GetArray`. `GetArray` is used for `ListValue`, and `Get` is used for the rests. The user should invoke either of them when he wants to get an Attribute value from `AttributeMap`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ListValue message is defined as:

message ListValue {
     repeated int32 ints = 1;
     repeated float floats = 2;
     repeated string strings = 3;
 }

And GetArray method only has one template parameter T. So, how the GetArray get ListValue with various type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one of ListValue field should be set. The protobuf 3 currently cannot make repeated field inside a oneof. So just make all ListValue a single message.

The tensorflow has a similar implementation

@reyoung reyoung force-pushed the feature/attribute_design_for_op branch 2 times, most recently from 0865ca4 to 84843d5 Compare June 28, 2017 10:14
* Simplify background description.
* Not using Error as return value, use PADDLE_ENFORCE
@reyoung reyoung force-pushed the feature/attribute_design_for_op branch from 84843d5 to e3a63d7 Compare June 28, 2017 10:16
@jacquesqiao jacquesqiao mentioned this pull request Jun 29, 2017
@reyoung reyoung force-pushed the feature/attribute_design_for_op branch from 679bf73 to 9bcd74c Compare June 29, 2017 09:10
};

/// Implementation of Contain
namespace details {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why named details ?

Copy link
Collaborator Author

@reyoung reyoung Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need define some function that should not be used by the user but can only be defined in the header file.

So mark them in namespace details could tell the user not to use them directly.

* mismatch or not contains that name.
*/
template <typename T>
bool Contain(const std::string& name) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

reader.GetArray("listInt", &actual);
reader.GetArray("listStr", &actualStr);
ASSERT_EQ(expected, actual);
ASSERT_EQ(expectedStr, actualStr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need ";" at the end of this line

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it. Done.

Add copy right and comments

Update design doc
@reyoung reyoung force-pushed the feature/attribute_design_for_op branch from 9bcd74c to 911113d Compare June 29, 2017 10:49
std::copy(field.begin(), field.end(), std::back_inserter(*vec)); \
}

ATTR_GETARRAY_IMPL(int, ints);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be with the same name style with ATTR_READER_GETVALUE_IMPL and ATTR_READER_ISTYPE_IMPL

ATTR_GETARRAY_IMPL => ATTR_READER_GETARRAY_IMPL

@reyoung reyoung mentioned this pull request Jun 29, 2017
* throw
*/
template <typename T>
T Get(const std::string& name) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just discuss, do we need a

T Get(const std::string& name, const T& default) const;

to set a default value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the design doc, people can use Contains to set a default value. So this is not a problem.

}

oneof value {
ListValue list = 1;
Copy link
Collaborator

@wangkuiyi wangkuiyi Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The oneof directive doesn't seem help much here -- it can locate a field in value but it cannot help us locate a field in ListValue. If our C++ code would still have to check which field is the one in ListValue, it would be easier just use the old plain syntax:

syntax = "proto3";

message Attribute {
  enum Type {
    INTS = 0;
    FLOATS = 1;
    STRINGS = 2;
    INT = 3;
    FLOAT = 4;
    STRING = 5;
  }
  Type type = 1;

  repeated int32 ints = 2;
  repeated float floats = 3;
  repeated string strings = 4;
  int32 int = 5;
  float float = 6;
  string string = 7;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax = "proto3";

message Attribute {
  repeated int32 ints = 1;
  repeated float floats = 2;
  repeated string strings = 3;
  optional int32 int = 4;
  optional float float = 5;
  optinoal string string = 6;
}

Maybe the Type is not useful, because we could use optional field and has_xxx() to detect AttributeType.


There are two frameworks implement `Attribute` concept in `protobuf`. They are [`caffe2`](https://github.com/caffe2/caffe2/blob/master/caffe2/proto/caffe2.proto#L98) and [`tensorflow`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/attr_value.proto#L16).

* Caffe2 uses `proto2` syntax. It treats all attributes as a list, and each attribute contains a `name`. Each time caffe2 read an attribute is searching a variable in a list. It is slow if the number of attributes is large. Caffe2 also mark all field as `optional`. It doesn't ensure `one of` attribute value is set.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprise to know that Caffe2 searches attributes in a protobuf list. The right way is to load from protobuf field repeated Attrbribute attrs = x; into C++ data structure map<string, Attribute> and search in the C++ data structure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my mistake, Caffe2 is loading all attribute into memory first.

}
```

In `OperatorDescription` message, there should be a field like this:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OperatorDescription => OperatorDesc ?


```cpp
using AttributeMap = google::protobuf::Map<std::string, Attribute>;
class AttributeReader {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AttributeReader => Attributes

This is not a reader, it holds and searches attributes as well.

T Get(const std::string& name) const;

// Get attribute with a array of type T, which name is `name`
template <typename T>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if an attribute was marked int in the protobuf message, but the user tries to retrieve its string value? How can we check and find such kind of errors?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we use optional, we can detect which type is set in protobuf, and assert fail when type mismatch.

// Is that `name` attribute with type T in map or not.
// T could be int, float, string and std::vector of them
template <typename T>
bool Contains(const std::string& name) const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contains => IsType

For example,

if (IsType<std::vector<string>>("name")) {
  ...
}

Actually, maybe it would be easier to have Has and Type instead

class Attributes {
 public:
  bool Has(const std::string& name) const {
    return attrs_.find(name) != attrs_.end();
  }

  const std::type_info& Type() const(const std::string& name) const {
    PADDLE_ENFORCE(Has(name));
    switch (attrs_[name].type()) {
    case paddle::framework::Attribute::INTS:
      return typeid(int);
    ...
    }
  }
};

class CosineOp : public OperatorBase {
public:
void InitializeAttribute(const AttributeReader& attrs) {
if (attrs.Contains<float>("scale")) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like a smart idea -- every time we change an attribute in the private: section, we must also change this function accordingly.

};
```

`InitializeAttribute` will be invoked by `CreateOperator`. Since `InitializeAttribute ` could throw an EnforceNotMet, a `unique_ptr` is used to make code exception-safe.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this document is not complete. I cannot find the part describing macros that define attributes of an operator?

```

`InitializeAttribute` will be invoked by `CreateOperator`. Since `InitializeAttribute ` could throw an EnforceNotMet, a `unique_ptr` is used to make code exception-safe.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I am curious about how the Python API can fill in the protobuf field OperatorDesc::attrs, so that the C++ code could load.

Copy link
Member

@jacquesqiao jacquesqiao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will discuss more about operator attr and registry

@reyoung
Copy link
Collaborator Author

reyoung commented Aug 2, 2017

Closed because it has been done.

@reyoung reyoung closed this Aug 2, 2017
@reyoung reyoung deleted the feature/attribute_design_for_op branch October 28, 2017 22:19
heavengate pushed a commit to heavengate/Paddle that referenced this pull request Aug 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants